#include "Classe_graphe.h"
#include "Classe_point.h"

#include <iostream>
#include <vector>
#include <cmath>
#include <cstdlib>

Graphe::Graphe(void) {
  /*
   * Pas de points a definir, donc on touche pas au vector, il n'y a pas non plus besoin d'alloue le tableau des segments
   */
  this->iNbPoint = 0;
  this->point_dist = NULL;

  std::cout << "Constructeur par defaut" << std::endl;
}

Graphe::Graphe(const int N) {
  this->point_dist = NULL;
  this->iNbPoint = N;
  this->ArrayPoint.resize(this->iNbPoint);
  
  for(int n=0;n<N;n++) {
    double A;
    double B;
    std::cout << "saisir X et Y pour " << n+1 << "point " << std::endl;
    std::cin >> A;
    std::cin >> B;
    this->ArrayPoint[n].setX(A);
    this->ArrayPoint[n].setY(B);
  }

  calculSegments();

  std::cout << "Constructeur JOUJOUX" << std::endl;
}

Graphe::Graphe(double const *const listX, double const *const listY, const int N) {
  this->point_dist = NULL;
  this->iNbPoint = N;
  // on alloue de la place pour N points dans le vecteur
  this->ArrayPoint.reserve(N);
  
  // On recopie les points dans le vector
  for(int i=0; i<N; i++) {
    Point pt(listX[i], listY[i]);
    this->ArrayPoint[i] = pt;
  }

  // Maintenant qu'on a les points, on calcule la distance entre tous les points
  calculSegments();

  std::cout << "Constructeur 2" << std::endl;
}

Graphe::Graphe(Point const *const Plist, const int N) {
  this->point_dist = NULL;
  this->iNbPoint = N;
  // on alloue la place de N points pour le vecteur
  this->ArrayPoint.reserve(N);

  for(int i=0; i<N; i++) {
    this->ArrayPoint[i] = Plist[i];
  }
  
  calculSegments();
  
  std::cout << "Constructeur 3" << std::endl;
}

Graphe::Graphe(const std::vector<Point> &Plist ) {
  this->point_dist = NULL;
  this->ArrayPoint = Plist; // utilisation de l'operateur '=' surcharge pour les vectors (deja defini dans la lib standard)
  this->iNbPoint = Plist.size();

  calculSegments();

  std::cout << "Constructeur 4" << std::endl;
}

Graphe::Graphe(const Graphe &Poly) {
  this->point_dist = NULL;
  this->iNbPoint = Poly.iNbPoint;
  
  this->ArrayPoint.reserve(Poly.iNbPoint);
  for(int i=0; i<Poly.iNbPoint; i++) {
    this->ArrayPoint[i] = Poly.ArrayPoint[i];
  }
  
  // On doit recopier le contenu du tableau, pas le pointeur !!!
  this->point_dist = new double[Poly.iNbPoint];
  for(int i=0; i<Poly.iNbPoint; i++) {
    this->point_dist[i] = Poly.point_dist[i];
  }
  
  std::cout << "Constructeur 5" << std::endl;
}

Graphe::~Graphe(void) {
  // on desalloue le tableau de segment car on a fait le new double[] plus haut
  if(this->point_dist != NULL) {
    delete[] this->point_dist;
  }

  // on vide le vector
  this->ArrayPoint.clear();

  std::cout << "Destructeur graphe" << std::endl;
}

void Graphe::printPoints(void) const {

  if(this->iNbPoint == 0) {
    std::cout << "Le graphe est vide, rien a afficher !" << std::endl;
  } else {
    for (int i=0; i<this->iNbPoint; i++) {
      std::cout << " (" << this->ArrayPoint[i].getX() << ", " << this->ArrayPoint[i].getY() << ")\n";
    }
  }
  
}

Point Graphe::getValue(int const idx) const {
  // on fait un check de l'argument pour voir si l'indice est valide, sinon on quitte le programme
  if(idx >= this->iNbPoint || idx < 0) {
    std::cout << "Out of range access" << std::endl;
    exit(-1);
  }
  
  return this->ArrayPoint[idx];
}

int Graphe::getSize(void) const {
  return this->iNbPoint;
}

double Graphe::getPerimetre(void) const {
  double perimetre = 0.0;

  for(int i=0; i<this->iNbPoint; i++) {
    perimetre += this->point_dist[i];
  }
  
  return perimetre;
}

double Graphe::getAire(void) const {
  double aire = 0.0;

  for(int i=0; i<this->iNbPoint; i++) {
      Point p_n = this->ArrayPoint[i % this->iNbPoint];
      Point p_n_1 = this->ArrayPoint[(i+1) % this->iNbPoint];
      aire += std::abs( p_n.getX() * p_n_1.getY() - p_n.getY() * p_n_1.getX() );
  }

  return 0.5 * aire;
}

void Graphe::calculSegments(void) {
  // Si on a 1 point, on ne va pas pouvoir calculer la distance entre les points
  if(this->iNbPoint <= 1) {
    this->point_dist = NULL;
  } else {
    
    // Dans le cas ou on va mettre a jour les distances apres ajout de nouveaux points
    // on doit desallouer le tableau courant
    if(this->point_dist != NULL) {
      delete[] this->point_dist;
      this->point_dist = NULL;
    }
    
    this->point_dist = new double[this->iNbPoint];
    
    for(int i=0; i<this->iNbPoint; i++) {
      this->point_dist[i] = this->ArrayPoint[i % this->iNbPoint].distance(this->ArrayPoint[(i+1) % this->iNbPoint]);
    }
    
  }
}

void Graphe::addPoints(Point const *const Plist, const int N) {
  // on realloue de la place dans le vecteur pour faire contenir les nouveaux points
  this->ArrayPoint.reserve(N + this->iNbPoint);

  for(int i=0; i<N; i++) {
    this->ArrayPoint[iNbPoint + i] = Plist[i];
  }
  
  this->iNbPoint += N;

  // on met a jour la taille des segments
  calculSegments();
}
